﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace Sofire.Data.XSql
{
    internal class XSqlNode : XSqlNameNodeBase, IXSqlNode
    {
        public const string NodeName = "xsql";

        public override XSqlType Type
        {
            get { return XSqlType.XSql; }
        }

        protected XSqlCacheMode _CacheMode = XSqlCacheMode.None;
        public XSqlCacheMode CacheMode
        {
            get
            {
                return this._CacheMode;
            }
            set
            {
                this._CacheMode = value;
            }
        }
        private string _Ignores;
        public string Ignores
        {
            get
            {
                return this._Ignores;
            }
            set
            {
                this._Ignores = value;
                if(this._Ignores != null) this._ignoreArray = this._Ignores.Split(',');
            }
        }

        private string[] _ignoreArray;

        public override XObject CreateXObject()
        {
            return this.CreateXObject(NodeName);
        }

        protected virtual XObject CreateXObject(string nodeName)
        {
            var element = new XElement(nodeName);

            element.SetAttributeValue(XSqlAttributes.Name, this.Name);
            element.SetAttributeValue(XSqlAttributes.Ignores, this._Ignores);

            switch(this._CacheMode)
            {
                case XSqlCacheMode.Values:
                    element.SetAttributeValue(XSqlAttributes.Cache, "values");
                    break;
                case XSqlCacheMode.Names:
                    element.SetAttributeValue(XSqlAttributes.Cache, "names");
                    break;
                case XSqlCacheMode.Forever:
                    element.SetAttributeValue(XSqlAttributes.Cache, "forever");
                    break;
            }
            this.CreateXChildren(element);
            return element;
        }

        protected override void Load(XmlNode node)
        {
            base.Load(node);
            this.Ignores = this.TryGetAttribute(node, XSqlAttributes.Ignores);

            switch(this.TryGetAttribute(node, XSqlAttributes.Cache))
            {
                case "values":
                    this._CacheMode = XSqlCacheMode.Values;
                    break;
                case "names":
                    this._CacheMode = XSqlCacheMode.Names;
                    break;
                case "forever":
                    this._CacheMode = XSqlCacheMode.Forever;
                    return;
                default:
                    return;
            }
            Cacher = new Dictionary<int, ExecuteCommand>();
        }

        public override string Compile(XSqlParameterCollection parameters)
        {
            return this.Children.Compile(parameters);
        }

        private readonly static System.Text.RegularExpressions.Regex TrimRegex = new System.Text.RegularExpressions.Regex(@"^[\s \r\n\t]*\n", System.Text.RegularExpressions.RegexOptions.Multiline | System.Text.RegularExpressions.RegexOptions.Compiled);

        private Dictionary<int, ExecuteCommand> Cacher;
        private ExecuteCommand CacheCommand;
        public ExecuteCommand CreateCommand(XSqlParameterCollection parameters)
        {
            int hashCode;
            ExecuteCommand command;
            switch(this._CacheMode)
            {
                case XSqlCacheMode.Values:
                    hashCode = XSqlCacheHelper.GetValuesKey(parameters);
                    break;
                case XSqlCacheMode.Names:
                    hashCode = XSqlCacheHelper.GetNamesKey(parameters);
                    break;
                case XSqlCacheMode.Forever:
                    if(CacheCommand == null) lock(this) if(CacheCommand == null) CacheCommand = this.InnerCreateCommand(parameters);
                    command = CacheCommand;
                    goto FOREVER;
                default: return this.InnerCreateCommand(parameters);
            }
            lock(Cacher)
            {
                if(!Cacher.TryGetValue(hashCode, out command))
                {
                    command = this.InnerCreateCommand(parameters);
                    Cacher.Add(hashCode, command);
                }
            }
        FOREVER:
            command = command.Clone() as ExecuteCommand;

            var pre = this.Document.Engine.ParameterPrefixWithCollection;
            foreach(var p in parameters)
            {
                var p2 = command.Parameters[pre + p.Name];
                if(p2 == null) continue;
                p2.Value = p.Value;
            }
            return command;
        }

        private ExecuteParameterCollection CreateParameters(XSqlParameterCollection parameters)
        {
            ExecuteParameterCollection eps = new ExecuteParameterCollection();
            if(this._ignoreArray != null)
            {
                foreach(var name in this._ignoreArray)
                {
                    parameters.Remove(name);
                }
            }
            var pre = this.Document.Engine.ParameterPrefixWithCollection;
            foreach(var p in parameters)
            {
                p.Name = pre + p.Name;
                if(p.IsValid) eps.Add(p);
            }
            return eps;
        }

        protected virtual ExecuteCommand InnerCreateCommand(XSqlParameterCollection parameters)
        {
            var s = this.Compile(parameters);
            if(s == null) throw new XSqlException(this, XSqlError.InvalidResult);
            var commandText = TrimRegex.Replace(s.TrimEnd(), string.Empty);
            return new ExecuteCommand(commandText, this.CreateParameters(parameters));
        }
    }

    internal class XSqlProcedureNode : XSqlNode, IXSqlProcedureNode
    {
        new public const string NodeName = "proc";

        public override XSqlType Type
        {
            get
            {
                return XSqlType.Procedure;
            }
        }

        public XSqlProcedureNode()
        {
            _CacheMode = XSqlCacheMode.Forever;
        }

        public override XObject CreateXObject()
        {
            return this.CreateXObject(NodeName);
        }

        protected override ExecuteCommand InnerCreateCommand(XSqlParameterCollection parameters)
        {
            var command = base.InnerCreateCommand(parameters);
            command.CommandText = ">" + command.CommandText.Trim();
            return command;
        }
    }
}
